﻿//-----------------------------------------------------------------------
// <copyright file="OperationCompletedCommand.cs" company="AFS">
// Copyright (c) AFS. All rights reserved.
//
//    This file is part of Chatbox.
//
//    Chatbox is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Lesser General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    any later version.
//
//    Chatbox is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//    GNU Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public License
//    along with Chatbox. If not, see http://www.gnu.org/copyleft/lesser.html
// </copyright>
//----------------------------------------------------------------------- 

namespace ChatBox.UI.Windows.Main
{
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows;
    using System.Windows.Threading;

    using ChatBox.DomainObjects.ResultData;
    using ChatBox.DomainObjects.Settings;
    using ChatBox.DomainObjects.XmlFeedObjects;
    using ChatBox.Interfaces;
    using ChatBox.UI.Commands;
    using ChatBox.UI.Controls;
    using ChatBox.UI.ViewModels;
    using ChatBox.UI.Windows.NewMessagePopup;
    using ChatBox.Utility;

    /// <summary>
    /// Command to update the the main window view model for Operation completed messages 
    /// </summary>
    public class OperationCompletedCommand : GenericCommandBase<OperationResult>
    {
        /// <summary>
        /// The view model to update
        /// </summary>
        private readonly MainWindowViewModel viewModel;

        /// <summary>
        /// Source of user interface settings
        /// </summary>
        private readonly IUISettingsRepository uiSettingsRepository;

        /// <summary>
        /// Initializes a new instance of the OperationCompletedCommand class
        /// </summary>
        /// <param name="viewModel">the main window view model to update</param>
        /// <param name="uiSettingsRepository">Source of user interface settings</param>
        public OperationCompletedCommand(MainWindowViewModel viewModel, IUISettingsRepository uiSettingsRepository)
        {
            this.viewModel = viewModel;
            this.uiSettingsRepository = uiSettingsRepository;
        }

        /// <summary>
        /// Execute the command
        /// </summary>
        /// <param name="value">the operation result</param>
        public override void ExecuteOnValue(OperationResult value)
        {
            Application application = Application.Current;
            Dispatcher dispatcher = application == null ? null : application.Dispatcher;

            if (dispatcher != null)
            {
                // get onto the right thread
                dispatcher.Invoke(DispatcherPriority.Normal, 
                    new OperationResultHandler(this.ReadOperationResultSync), value);
            }
        }

        /// <summary>
        /// Execute the command on the Ui thread
        /// </summary>
        /// <param name="operationResult">the operation result</param>
        private void ReadOperationResultSync(OperationResult operationResult)
        {
            if (this.viewModel == null)
            {
                return;
            }

            if (this.viewModel.ChatEngine == null)
            {
                return;
            }

            this.viewModel.ReadEngineLogOn();

            this.RecordOperationInStatusLines(operationResult);
            this.CapStatusLines();

            this.UpdateLogonState();

            if (operationResult.Success)
            {
                this.ReadSucessResult(operationResult);
            }
            else
            {
                this.ReadErrorResult(operationResult);
            }
        }

        /// <summary>
        /// Read a sucessful operation for the result
        /// </summary>
        /// <param name="operationResult">the operation result</param>
        private void ReadSucessResult(OperationResult operationResult)
        {
            ChatDataResult chatDataResult = operationResult as ChatDataResult;
            if (chatDataResult != null)
            {
                this.ShowChatData(chatDataResult);
            }

            OtherUsersDataResult otherUsersResult = operationResult as OtherUsersDataResult;
            if (otherUsersResult != null)
            {
                this.viewModel.ShowOtherUsers();
            }

            this.viewModel.Throbber.Throb(ConnectionState.DataReceived);
            this.viewModel.StatusText = operationResult.Message;
        }

        /// <summary>
        /// Read an error operation for the result
        /// </summary>
        /// <param name="operationResult">the operation result</param>
        private void ReadErrorResult(OperationResult operationResult)
        {
            this.ShowError(operationResult);

            if (operationResult is LogOnResult)
            {
                this.viewModel.Throbber.LogOnState = ConnectionState.Error;
            }

            UISettingsData uiSettings = this.uiSettingsRepository.ReadSettings();
            if (uiSettings.PlaySoundOnError)
            {
                this.viewModel.NotificationSoundPlayer.PlayError();
            }
        }

        /// <summary>
        /// Show the error
        /// </summary>
        /// <param name="operationResult">the operation result</param>
        private void ShowError(OperationResult operationResult)
        {
            this.viewModel.Throbber.Throb(ConnectionState.Error);
            string message = operationResult.Message;
            if (operationResult.Error != null)
            {
                message += " " + operationResult.Error.Message;
            }

            this.viewModel.StatusText = message.Trim();
        }

        /// <summary>
        /// Show the chat data
        /// </summary>
        /// <param name="chatDataResult">the chat data result</param>
        private void ShowChatData(ChatDataResult chatDataResult)
        {
            if (chatDataResult.ServerDataType == ServerDataType.PublicMessages)
            {
                // if we got public chat messages, even an empty response, hide the "loading" Ui element
                this.ShowPublicChatData(chatDataResult);
            }
            else if ((chatDataResult.ServerDataType == ServerDataType.PrivateMessages) && (chatDataResult.Sequence > 0))
            {
                // trim them down to the last few 
                this.ShowPrivateChatData(chatDataResult);
            }
        }

        /// <summary>
        /// Show the public chat data
        /// </summary>
        /// <param name="chatDataResult">the chat data result</param>
        private void ShowPublicChatData(ChatDataResult chatDataResult)
        {
            this.viewModel.LoadingTextVisibility = Visibility.Collapsed;

            if ((chatDataResult.Messages.Count() > 0) && this.viewModel.HasFirstPublicMessages)
            {
                UISettingsData uiSettings = this.uiSettingsRepository.ReadSettings();
                if (uiSettings.PlaySoundOnNewPublicMessage)
                {
                    this.viewModel.NotificationSoundPlayer.PlayBeep();
                }
            }

            this.viewModel.HasFirstPublicMessages = true;
        }

        /// <summary>
        /// Show the private chat data
        /// </summary>
        /// <param name="chatDataResult">the chat data result</param>
        private void ShowPrivateChatData(ChatDataResult chatDataResult)
        {
            const int MaxInlinePrivateMessage = 10;

            UISettingsData uiSettings = this.uiSettingsRepository.ReadSettings();

            // got private messages after the initial load. play a sound now?
            if (uiSettings.PlaySoundOnNewPrivateMessage)
            {
                this.viewModel.NotificationSoundPlayer.PlayNotification();
            }

            if (uiSettings.PopupWindowOnNewPrivateMessage)
            {
                this.ShowNewMessagesPopup(chatDataResult.Messages.TakeLast(MaxInlinePrivateMessage).ToList());
            }

            if (uiSettings.InlineNewPrivateMessage)
            {
                this.viewModel.InlinePrivateMessages.AddRange(chatDataResult.Messages);

                // trim them down to the last few messages
                while (this.viewModel.InlinePrivateMessages.Count > MaxInlinePrivateMessage)
                {
                    this.viewModel.InlinePrivateMessages.RemoveAt(0);
                }

                this.viewModel.DisplayMessages();
            }
        }

        /// <summary>
        /// Show a popup window for the new private messages
        /// </summary>
        /// <param name="messages">the messages to show</param>
        private void ShowNewMessagesPopup(IList<Message> messages)
        {
            NewMessageData data = new NewMessageData
            {
                Messages = messages,
                HyperlinkContext = this.viewModel.HyperlinkContext
            };

            this.viewModel.ShowNewMessagePopupCommand.Execute(data);
        }

        /// <summary>
        /// Update the view model Logon status
        /// </summary>
        private void UpdateLogonState()
        {
            ConnectionState newState = this.viewModel.ChatEngine.IsLoggedOn ? ConnectionState.DataReceived : ConnectionState.NotConnected;
            this.viewModel.Throbber.LogOnState = newState;
        }

        /// <summary>
        /// Record an operation completed in the list of recent operations
        /// </summary>
        /// <param name="operationResult">the oepration result to record</param>
        private void RecordOperationInStatusLines(OperationResult operationResult)
        {
            StatusLineViewModel operationViewModel = new StatusLineViewModel
            {
                ConnectionState = operationResult.Success ? ConnectionState.DataReceived : ConnectionState.Error,
                Message = operationResult.MessageOrErrorMessage,
                OperationTime = operationResult.OperationTime
            };

            this.viewModel.StatusLines.Add(operationViewModel);
        }

        /// <summary>
        /// Remove old status lines
        /// </summary>
        private void CapStatusLines()
        {
            const int MaxLines = 8;
            while (this.viewModel.StatusLines.Count > MaxLines)
            {
                this.viewModel.StatusLines.RemoveAt(0);
            }
        }
    }
}
